<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>透镜的扫描电流控制器</title>
</head>
<center>
<body>
    <style>
        .config_input {
            width: 60px;
        }
        #control_pannel {
            border: 2px solid green   ;
            padding: 5px;
            width: fit-content;
        }
        .pp {
            color: red;
        }
        .single_channel {
            margin: 2px;
            border: 1px solid blue;
            width: fit-content;
            padding: 2px;
        }
        .state_prompt {
            color: red;
        }
        h4 {
            color: red;
        }
    </style>
    <div>
        <h2>透镜的扫描电流控制器</h2>
        <h4>请先选择通信端口</h4>
        <p>刷新页面会自动关闭通信端口</p>
        <button id="com_chooser">选择通信端口</button>
        <input class="config_input" type="text" id="com_product_id" readonly="readonly" placeholder="product id将会显示">
        <input class="config_input" type="text" id="com_vendor_id" readonly="readonly" placeholder="vendor id将会显示">
        <button id="port_closer" disabled="true" onclick="try_to_close_port()">关闭端口</button>
    </div>
    <p>左边为稳定（stable）电流模式，右边为扫描（wobble）电流模式</p>
        <p>输入电流值，然后点击对应的按钮（一定记得点击设置），设置通道的电流值</p>
        <p>勾选所需的通道并单击“使能勾选的通道”，各个通道将从0相位开始输出设定的wobble电压</p>
        <p>点击同一个通道的“设置电流为0”，此通道即便启用也会输出0电流</p>
    <p>最大电流0.4A，别输入太大的电流哦！防止烧线圈......</p>
    <p>输入时不用输入单位</p>
   
    
    <div>
        <!-- <p>以下是控制面板各个按钮和文本框的示例说明</p>
        <span>
            <span>通道号xxx</span>
            <span>当前状态：停止/恒定/wobble</span>
            <input class="config_input" type="text" readonly="readonly" value="输入目标电流，单位A">
            <input class="config_input" type="button" disabled="true" value="点击输出或停止输出">
            <input class="config_input" type="text" readonly="readonly" value="输入最大电流值，单位A">
            <input class="config_input" type="text" readonly="readonly" value="输入最小电流值，单位A">
            <input class="config_input" type="text" readonly="readonly" value="周期，单位s">
            <input class="config_input" type="button" disabled="true" value="点击输出或停止输出">
        </span> -->
    </div>
    <br>

    <!-- 就把所有面板堆起来。未来可以考虑用js生成html -->
    <div id="control_pannel">
        
        <div id="enable_pannel">
            <button onclick="enable_some_channels()">使能勾选的通道</button>
            <button onclick="disable_all_channels()">禁用全部通道</button>
        </div>
    </div>

    <script>
        /**
         * @file wobble扫描电流控制器 
         * @author 陈卓勋 (chenzhuoxun20@mails.ucas.ac.cn)
         * @brief 在生物岛实验室玩。电子光学电子枪对中需要对偏转线圈施加一个扫描电流，
         * 需要一个wobble 扫描电流的控制界面，向下位机发送数据，从而控制电流源。
         * 这是一个控制扫描电流的网页。
         * @version 0.2
         * @date 2022-08-10
         * 
         * @copyright Copyright (c) 2022
         * 
         */
        
        // 生成用户界面

        const channel_names = ["通道1-sxu","通道2-syu","通道3-sxl","通道4-syl","通道5-备用5","通道6-备用2","通道7-备用3","通道8-备用4"];
        
        document.body.onload = addElement;

        function addElement () {
            for(let i = 0;i<8;i+=1) {
                var inner_data; 
                let sub_span;
                let numstr;
                numstr = i.toString();
                sub_span = document.createElement("div");
                sub_span.classList.add("single_channel");
                inner_data = ''
                    + '<input type="checkbox" id="check_'+numstr+'">'
                    + '<span>'+ channel_names[i] +'</span>'
                    + '<span> | 启用状态：</span>'
                    + '<span id="enabledd_'+numstr+'" class="state_prompt" >不知道  </span>'
                    + '<span> | 电流模式：</span>'
                    + '<span id="state_'+numstr+'" class="state_prompt" >啥也没有</span>'
                    + '<span> | </span>'
                    + '<button onclick="set_stop('+numstr+')">设置电流为0</button>'
                    + '<span> | </span>'
                    + '<input class="config_input" type="number" max="0.4" min="-0.4" id="stable_current_'+numstr+'" placeholder="稳定(A)">'
                    + '<button onclick="set_stable('+numstr+')" >设置稳定</button>'
                    + '<span> | </span>'
                    + '<input class="config_input" type="number" max="0.4" min="-0.4" id="wobble_max_'+numstr+'" placeholder="最大(A)">'
                    + '<input class="config_input" type="number" max="0.4" min="-0.4" id="wobble_min_'+numstr+'" placeholder="最小(A)">'
                    + '<input class="config_input" type="number" max="10" min="1" id="wobble_period_'+numstr+'" placeholder="周期(s)">'
                    + '<button onclick="set_wobble('+numstr+')">设置wobble</button>'
                    + '<br>';
                sub_span.innerHTML = inner_data;
                let currentDiv = document.getElementById("control_pannel");
                currentDiv.appendChild(sub_span);
            }
        }

        // var 
        // 通道统一编号1-8
        // 数组的index要减1
        const STABLE = 2;
        const WOBBLE = 1;
        const STOP = 0
        const CHANNEL_NUM = 8;
        const INTERVAL_OF_ALL_CHANNEL_SENDING = 2000; // unit: ms
        const INTERVAL_OF_CHANNEL_CHECKING = 200; // unit: ms
        const MAX_CURRENT = 0.4;
        const MAX_CURRENT_DIGIT = 0xfff;
        
        var i,j;

        // 通信协议示例
        const data = new Uint8Array([
            0x55,   // index 0,  byte 1:     start
            0x01 ,   // index 1,  byte 2:    channel
            0x01,   // index 2,  byte 3:     mode 1.stable, 2.wobble, 3.enable signal
            0x00,   // index 3,  byte 4:     direction
            0x0f,   // index 4,  byte 5:     current absolute high
            0xff,   // index 5,  byte 6:     current absolute low
            0x00,   // index 6,  byte 7:     direction
            0x0f,   // index 7,  byte 8:     current absolute high
            0xff,   // index 8,  byte 9:     current absolute low
            0x00,   // index 9,  byte 10:    period
            0xAA,   // index 10, byte 11:    end
        ]);

        // 将用户输入的数值转化为通信协议中所需的电流方向、高字节和低字节
        function divide_number(number) {
            var direction = number >=0 ? 0 : 1;
            var digitalized = Math.floor(Math.abs(number)/MAX_CURRENT*MAX_CURRENT_DIGIT);
            var high =  digitalized >> 8;
            var low = digitalized & 0xff;
            return [direction,high,low];
        }

        // 用此来测试divide_number 函数的功能
        // console.log(divide_number(-0.4));
        // var a= divide_number(-0.2);
        // console.log(a[0],a[1],a[2]);

        // 数组存储所有控制器的状态，稳定电流(stable)、扫描电流(wobble)、关闭(stop)
        // 初始化为关闭状态
        var states = new Array();
        for(i = 0;i<CHANNEL_NUM;i++)
            states.push(STOP);

        // 检测浏览器是否支持串口
        if(!("serial" in navigator))
        {
            alert("你的浏览器不支持串口，请用最新版的Chrome或Edge浏览器打开");
        }
        
        // 给端口选择按钮增加点击响应函数
        const com_chooser_button = document.querySelector('#com_chooser');
        var port;   // 端口
        var writer; // 写端口

        // 此函数被调用后，在time毫秒之后向chan通道发送用户设定的数据。
        function wait_for_sending(chan,time)
        {
            console.log("wait for sending",chan);
            var k;
            k = chan; // channel to be checked
            setTimeout(() => {
                switch (states[k]) {
                    case STOP:  // 停止状态，发送0电流
                        document.getElementById("state_" + k.toString()).innerHTML = "stop"
                        var stop_send;
                        stop_send = new Uint8Array([0x55,k+1,1,
                                                    0,0,0,
                                                    0xff,0xff,0xff,
                                                    0xff,0xaa]);
                        writer.write(stop_send);
                        break;
                    case WOBBLE:    // 扫描电流模式，发送高电流、低电流、每秒的周期数
                        document.getElementById("state_" + k.toString()).innerHTML = "wobble"
                        var max,min,period_per_second,max_divide,min_divide;
                        max = Number(document.getElementById("wobble_max_"+k.toString()).value);
                        min = Number(document.getElementById("wobble_min_"+k.toString()).value);
                        if(Math.abs(max) > 0.4 || Math.abs(min) >0.4)
                        {
                            alert("电流太大了，输入电流绝对值放在0.4A及其以内吧");
                            break;
                        }
                        max_divide = divide_number(max);
                        min_divide = divide_number(min);
                        period_in_second = Number(document.getElementById("wobble_period_"+k.toString()).value);
                        var wobble_send;
                        wobble_send = new Uint8Array([0x55,k+1,2,
                                                    max_divide[0],max_divide[1],max_divide[2],
                                                    min_divide[0],min_divide[1],min_divide[2],
                                                    Math.floor(period_in_second),0xaa]);
                        writer.write(wobble_send);
                        break;
                    case STABLE:    // 稳定电流状态，发送电流大小和方向
                        document.getElementById("state_" + k.toString()).innerHTML = "stable"
                        var stable_current,stable_divide;
                        stable_current = Number(document.getElementById("stable_current_"+k.toString()).value);
                        stable_divide = divide_number(stable_current);
                        var stable_send;
                        // 判断是否超过最大电流
                        // if(Math.abs(stable_current) > MAX_CURRENT)
                        //     stable_current = Math.sign(stable_current) * MAX_CURRENT;
                        if(Math.abs(stable_current) > 0.4)
                        {
                            alert("电流太大了，输入电流绝对值放在0.4A及其以内吧");
                            break;
                        }
                        stable_send = new Uint8Array([0x55,k+1,1,
                                                    stable_divide[0],stable_divide[1],stable_divide[2],
                                                    0xff,0xff,0xff,
                                                    0xff,0xaa]);
                        writer.write(stable_send);
                        break; 
                    default:
                        alert("unknown channel state");
                        break;
                }
            }, time);
        }

        // 为端口选择按钮增加点击异步响应函数
        com_chooser_button.addEventListener('click', async function() {
            // Prompt user to select any serial port.
            port = await navigator.serial.requestPort();
            if(port != null)
            {
                // Wait for the serial port to open.
                await port.open({ baudRate: 9600 });
                console.log("Open",port,"at baud rate 9600!");
                const {usbProductId, usbVendorId} = port.getInfo();
                console.log("Product id: ",usbProductId,"vendor id: ",usbVendorId);
                document.getElementById("com_product_id").value = usbProductId;
                document.getElementById("com_vendor_id").value = usbVendorId;
                writer = port.writable.getWriter();
                document.getElementById("port_closer").disabled = false;
                // 一直发送的版本
                // setInterval(() => {
                //     for(j = 1; j<=CHANNEL_NUM;j++)
                //     {
                //         wait_for_sending(j,interval_of_channel_checking *j);
                //     }
                // }, interval_of_all_channel_sending);
            }
        });

        // 端口关闭函数
        async function try_to_close_port(){
            writer.releaseLock();
            await port.close();
            document.getElementById("port_closer").disabled = true;
            document.getElementById("com_product_id").value = "";
            document.getElementById("com_vendor_id").value = "";
        }

        // 每个发送和停止的响应函数
        function set_stable(channel){
            states[channel] = STABLE;
            wait_for_sending(channel,1);
        }
        function set_stop(channel){
            states[channel] = STOP;
            wait_for_sending(channel,1);

        }
        function set_wobble(channel){
            states[channel] = WOBBLE;
            wait_for_sending(channel,1);

        }
        async function send_enable_info(enable_byte) {
            var cmd = new Uint8Array(11);
            cmd.fill(0xff);
            cmd[0] = 0x55;
            cmd[2] = 3;     // wobble 输出使能
            cmd[3] = enable_byte;
            cmd[10] = 0xaa;
            await writer.write(cmd);
        }
        function enable_some_channels() {
            let enable_byte = 0x0;
            for(let i = 0; i<8 ; i+=1) {
                let str;
                str = i.toString();
                if(document.getElementById("check_"+str).checked == true) {
                    // console.log(i);
                    document.getElementById("enabledd_"+str).innerHTML="已启用";
                    enable_byte |= 0x1 << i;
                }
                else {
                    document.getElementById("enabledd_"+str).innerHTML="已禁用";
                }
            }
            send_enable_info(enable_byte);
        }
        function disable_all_channels() {
            for(let j = 0; j<8 ; j+=1) {
                document.getElementById("enabledd_" + j.toString()).innerHTML = "已禁用"
            }
            send_enable_info(0x0);
        }
        
        
    </script>
</body>
</center>
</html>